// Copyright (c) 2019 Presto Labs Pte. Ltd.
// Author: jaewon

#include "coin1/fastfeed/view/book_builder.h"

#include <glog/logging.h>

#include "coin1/fastfeed/view/view_collection.h"

namespace fastfeed {
namespace view {

void BookBuilder::Init(ViewCollection* view_collection) {
  view_collection->SubscribeFeed(
      feed_type(),
      symbol(),
      std::bind(&BookBuilder::OnFeed, this, std::placeholders::_1));
  view_collection->SubscribeFeed(
      proto::FEED_TYPE_METADATA,
      symbol(),
      std::bind(&BookBuilder::OnFeed, this, std::placeholders::_1));
  InitBuilder();
}

bool BookBuilder::UpdateByProto(const proto::Feed& feed) {
  switch (feed.data_type()) {
    case proto::DATA_TYPE_BOOK_BUILDER_PARAMETER:
      return OnBookBuilderParameter(feed);

    case proto::DATA_TYPE_BOOK_SNAPSHOT:
      return OnBookSnapshot(feed);

    case proto::DATA_TYPE_BOOK_UPDATE:
      return OnBookUpdate(feed);

    default:
      return false;
  }
}

bool BookBuilder::OnBookSnapshot(const proto::Feed& feed) {
  UpdateHeader(feed);

  const auto& book_snapshot = feed.book_snapshot();
  ask_.Clear();
  for (const auto& level : book_snapshot.ask()) {
    if (level.qty() != 0) {
      ask_.SetLevel(level.price(), {level.price(), level.qty(), level.count()});
    }
  }
  bid_.Clear();
  for (const auto& level : book_snapshot.bid()) {
    if (level.qty() != 0) {
      bid_.SetLevel(level.price(), {level.price(), level.qty(), level.count()});
    }
  }
  return true;
}

bool BookBuilder::OnBookUpdate(const proto::Feed& feed) {
  const auto& book_update = feed.book_update();
  UpdateHeader(feed);

  std::optional<int64_t> max_bid_price;
  std::optional<int64_t> min_ask_price;

  for (const auto& level : book_update.ask()) {
    if (level.qty() != 0) {
      ask_.SetLevel(level.price(), {level.price(), level.qty(), level.count()});
      if (!min_ask_price.has_value() || min_ask_price > level.price()) {
        min_ask_price = level.price();
      }
    } else {
      ask_.EraseLevel(level.price());
    }
  }
  for (const auto& level : book_update.bid()) {
    if (level.qty() != 0) {
      bid_.SetLevel(level.price(), {level.price(), level.qty(), level.count()});
      if (!max_bid_price.has_value() || max_bid_price < level.price()) {
        max_bid_price = level.price();
      }
    } else {
      bid_.EraseLevel(level.price());
    }
  }
  CleanupBook(min_ask_price, max_bid_price);
  return true;
}

void BookBuilder::CleanupBook(
    const std::optional<int64_t>& min_ask_price,
    const std::optional<int64_t>& max_bid_price) {
  // clean up irregular points
  if (min_ask_price.has_value()) {
    // remove all bids >= min_ask_price
    // from inside, thus upper bound
    bid_.EraseUntilBound(min_ask_price.value());
  }
  if (max_bid_price.has_value()) {
    // remove all asks <= max_bid_price
    // from inside, thus upper bound
    ask_.EraseUntilBound(max_bid_price.value());
  }
}

BookIterator BookBuilder::FindAskLevelByPrice(int64_t price, bool return_next_level_if_not_exist)
    const {
  auto it = ask_.FindLevel(price, return_next_level_if_not_exist);
  return BookIterator(it);
}

BookIterator BookBuilder::FindBidLevelByPrice(int64_t price, bool return_next_level_if_not_exist)
    const {
  auto it = bid_.FindLevel(price, return_next_level_if_not_exist);
  return BookIterator(it);
}

std::optional<BookLevel> BookBuilder::GetAsk0() const {
  if (ask_.empty()) {
    return std::nullopt;
  }
  return ask_.begin()->second;
}

std::optional<BookLevel> BookBuilder::GetBid0() const {
  if (bid_.empty()) {
    return std::nullopt;
  }
  return bid_.begin()->second;
}

std::optional<BookLevel> BookBuilder::GetAskLevelByPrice(
    int64_t price,
    bool return_next_level_if_not_exist) const {
  auto it = ask_.FindLevel(price, return_next_level_if_not_exist);
  if (it == ask_.end()) {
    return std::nullopt;
  }
  return it->second;
}

std::optional<BookLevel> BookBuilder::GetBidLevelByPrice(
    int64_t price,
    bool return_next_level_if_not_exist) const {
  auto it = bid_.FindLevel(price, return_next_level_if_not_exist);
  if (it == bid_.end()) {
    return std::nullopt;
  }
  return it->second;
}

}  // namespace view
}  // namespace fastfeed
